#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <openssl/md5.h>
#include <errno.h>

#define PORT 8080
#define BUFFER_SIZE 1024
#define BLOCK_SIZE 4096

// 存储用户凭证（简化处理，实际应用中应加密存储）
typedef struct {
    char username[50];
    char password[50];
} User;

User validUsers[] = {
    {"user1", "pass1"},
    {"admin", "admin123"},
    {NULL, NULL} // 结束标记
};

// 检查用户名和密码是否有效
int validateUser(const char* username, const char* password) {
    for (int i = 0; validUsers[i].username != NULL; i++) {
        if (strcmp(username, validUsers[i].username) == 0 &&
            strcmp(password, validUsers[i].password) == 0) {
            return 1; // 验证成功
        }
    }
    return 0; // 验证失败
}

// 计算文件的MD5值
void calculateMD5(const char* filename, char* md5Str) {
    int fd;
    unsigned char data[BLOCK_SIZE];
    unsigned char md5Digest[MD5_DIGEST_LENGTH];
    MD5_CTX md5Context;
    ssize_t bytesRead;
    
    fd = open(filename, O_RDONLY);
    if (fd < 0) {
        perror("无法打开文件计算MD5");
        strcpy(md5Str, "");
        return;
    }
    
    MD5_Init(&md5Context);
    while ((bytesRead = read(fd, data, BLOCK_SIZE)) > 0) {
        MD5_Update(&md5Context, data, bytesRead);
    }
    MD5_Final(md5Digest, &md5Context);
    close(fd);
    
    // 转换为字符串
    for (int i = 0; i < MD5_DIGEST_LENGTH; i++) {
        sprintf(&md5Str[i*2], "%02x", (unsigned int)md5Digest[i]);
    }
}

// 获取文件大小
off_t getFileSize(const char* filename) {
    struct stat fileInfo;
    if (stat(filename, &fileInfo) < 0) {
        return -1;
    }
    return fileInfo.st_size;
}

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    
    // 创建socket文件描述符
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    
    // 设置socket选项
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    
    // 绑定socket到端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    
    // 监听连接
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    
    printf("服务器启动，监听端口 %d...\n", PORT);
    
    while (1) {
        // 接受客户端连接
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
            perror("accept");
            continue;
        }
        
        printf("新连接: %s:%d\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
        
        // 处理客户端登录
        char username[50], password[50];
        ssize_t valread = read(new_socket, buffer, BUFFER_SIZE);
        if (valread <= 0) {
            perror("读取登录信息失败");
            close(new_socket);
            continue;
        }
        
        // 解析用户名和密码
        if (sscanf(buffer, "%s %s", username, password) != 2) {
            send(new_socket, "登录失败: 格式错误", 20, 0);
            close(new_socket);
            continue;
        }
        
        // 验证用户
        if (!validateUser(username, password)) {
            send(new_socket, "登录失败: 用户名或密码错误", 28, 0);
            close(new_socket);
            continue;
        }
        
        send(new_socket, "登录成功", 9, 0);
        printf("用户 %s 登录成功\n", username);
        
        // 接收要下载的文件名
        memset(buffer, 0, BUFFER_SIZE);
        valread = read(new_socket, buffer, BUFFER_SIZE);
        if (valread <= 0) {
            perror("读取文件名失败");
            close(new_socket);
            continue;
        }
        
        char filename[256];
        strncpy(filename, buffer, sizeof(filename)-1);
        printf("客户端请求下载文件: %s\n", filename);
        
        // 检查文件是否存在
        int file_fd = open(filename, O_RDONLY);
        if (file_fd < 0) {
            send(new_socket, "文件不存在", 11, 0);
            close(new_socket);
            continue;
        }
        
        // 获取文件信息
        off_t file_size = getFileSize(filename);
        char md5Str[33] = {0};
        calculateMD5(filename, md5Str);
        
        // 发送文件信息：文件名 大小 MD5
        char fileInfo[BUFFER_SIZE];
        snprintf(fileInfo, BUFFER_SIZE, "%s %lld %s", filename, (long long)file_size, md5Str);
        send(new_socket, fileInfo, strlen(fileInfo), 0);
        
        // 等待客户端确认
        memset(buffer, 0, BUFFER_SIZE);
        valread = read(new_socket, buffer, BUFFER_SIZE);
        if (valread <= 0 || strcmp(buffer, "准备接收") != 0) {
            perror("客户端确认失败");
            close(file_fd);
            close(new_socket);
            continue;
        }
        
        // 处理文件块请求
        char blockBuffer[BLOCK_SIZE];
        while (1) {
            memset(buffer, 0, BUFFER_SIZE);
            valread = read(new_socket, buffer, BUFFER_SIZE);
            if (valread <= 0) {
                break;
            }
            
            // 客户端请求退出
            if (strcmp(buffer, "完成") == 0) {
                break;
            }
            
            // 解析请求的块偏移量
            off_t offset;
            if (sscanf(buffer, "块 %lld", &offset) != 1) {
                send(new_socket, "错误: 无效的请求格式", 23, 0);
                continue;
            }
            
            // 移动到文件指定位置
            if (lseek(file_fd, offset, SEEK_SET) != offset) {
                perror("lseek失败");
                send(new_socket, "错误: 无法定位文件", 20, 0);
                continue;
            }
            
            // 读取文件块
            ssize_t bytesRead = read(file_fd, blockBuffer, BLOCK_SIZE);
            if (bytesRead < 0) {
                perror("读取文件块失败");
                send(new_socket, "错误: 读取文件失败", 20, 0);
                continue;
            }
            
            // 发送块大小和数据
            char blockHeader[50];
            snprintf(blockHeader, sizeof(blockHeader), "大小 %zd\n", bytesRead);
            send(new_socket, blockHeader, strlen(blockHeader), 0);
            
            if (bytesRead > 0) {
                send(new_socket, blockBuffer, bytesRead, 0);
            }
        }
        
        printf("文件传输完成，关闭连接\n");
        close(file_fd);
        close(new_socket);
    }
    
    return 0;
}
